/*==========================================================================
 * 
 * HexTable.java
 * 
 * $Author: randallco $
 * $Revision: 1.8 $
 * $Date: 2008/05/04 16:06:53 $
 * 
 * Created on 22-Jan-2004
 * Created by Marcel Palko alias Randallco (randallco@users.sourceforge.net)
 *==========================================================================*/
package net.sourceforge.ehep.gui;

import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTError;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Font;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;

import net.sourceforge.ehep.EhepPlugin;
import net.sourceforge.ehep.core.*;

/**
 * @author Marcel Palko
 * @author randallco@users.sourceforge.net
 */
public class HexTable extends Composite {
	private Table table;
    private ArrayList<HexTableItem> items = new ArrayList<HexTableItem>();
	private HexEditorControl hexEditorControl;
	private boolean inDispose = false;
//	/**@author Sophia, Date: 06/20/09 */
//	private boolean bufOverwrite = false;
	private int initRowIndex = 0;
	private int bufSize = 0;

	/**
	 * Constructor
	 * @param parent
	 * @param hexEditorControl
	 * @param style
	 */
	public HexTable(Composite parent, HexEditorControl hexEditorControl, int style) {
		super(parent, SWT.NONE);
		this.hexEditorControl = hexEditorControl;

		table = new Table(this, style);
		setBackground(table.getBackground());
		setForeground(table.getForeground());
		setFont(table.getFont());
		
		addListener(SWT.Resize, new Listener() {
			public void handleEvent(Event e) {
				onResize();
			}
		});

		addListener(SWT.FocusIn, new Listener() {
			public void handleEvent(Event e) {
				onFocusIn();
			}
		});

		addListener(SWT.Dispose, new Listener() {
			public void handleEvent(Event e) {
				onDispose();
			}
		});
	}
	
	/**
	 * Constructor
	 * @author Sophia Ghins
	 * Date: 06/23/09
	 */
	public HexTable(){
	    super(new Shell(new Display()), SWT.NONE);
	    this.hexEditorControl = null;
	    table = new Table(this, SWT.NONE);

	}

	public HexEditorControl getHexEditorControl() {
		return this.hexEditorControl;
	}

	/**
	 * Adds the item at the end
	 * @param item
	 * @return
	 */
	public void addItem(HexTableItem item) {
		addItem(item, items.size());
	}
	
	/**
	 * Adds item at the specified location
	 * @param item
	 * @param index
	 */
	public void addItem(HexTableItem item, int index) {
		if (index < 0 || index > items.size()) throw new SWTError(SWT.ERROR_INVALID_ARGUMENT);
        items.add(index, item);
	}

	/**
	 * Gets the item from specified location
	 * @param index Zero-based index of the row
	 * @return HexTableItem
	 */
	public HexTableItem getItem(int index) {
		if (index < 0) return null;
		EhepPlugin.log("getItem(): table size=" + table.getItemCount() + ", index=" + index);
		return (HexTableItem) table.getItem(index).getData();
	}

	/**
	 * Removes all table items from the specified zero-based index to the end
	 * @param indexBegin
	 */
	public void remove(int indexBegin) {
		if (indexBegin >= items.size()) return;
		
		table.remove(indexBegin, items.size()-1);
		
        while (indexBegin < items.size()) {
            items.get(indexBegin).dispose();
            items.remove(indexBegin);
        } // while
		
		EhepPlugin.log("remove(): table.getItemCount()=" + table.getItemCount() + ", items.size=" + items.size());
		if (items.size() != table.getItemCount()) EhepPlugin.log("remove(): ++++++++ FATAL ERROR! +++++++");
	}

	/**
	 * Gets the table column
	 * @param index of table column
	 * @return table column
	 */
	public TableColumn getColumn(int index) {
		return table.getColumn(index);
	}
    
    /**
     * Returns the zero-based index of the row
     * @param row
     * @return Zero-based index of the row or -1 if row is null
     */
    public int getRowIndex(HexTableItem row) {
        return (row != null) ? items.indexOf(row) : -1;
    }
	
	/**
	 * Returns the number of table items (rows)
	 * @return number of table rows
	 */
	public int getItemCount() {
		return items.size();
	}

	/**
	 * Selects the table item
	 * @param index
	 */
	public void select(int index) {
		table.select(index);
	}
	
	/**
	 * Sets the widget font.
	 * <p>
	 * When new font is null, the font reverts
	 * to the default system font for the widget.
	 *
	 * @param font the new font (or null)
	 * 
	 * @exception SWTError <ul>
	 *          <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
	 *          <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
	 * </ul>
	 * </p>
	 */
	public void setFont(Font font) {
		super.setFont(font);
		table.setFont(font);
	}

	/**
	 * Sets the header visibility
	 * @param show
	 */
	public void setHeaderVisible(boolean show) {
		table.setHeaderVisible(show);
	}
	
	/**
	 * Sets the table line visibility
	 * @param show
	 */
	public void setLinesVisible(boolean show) {
		table.setLinesVisible(show);
	}
	
	/**
	 * Sets the selection.
	 * <p>
	 * @param items new selection
	 *
	 * @exception SWTError <ul>
	 *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread
	 *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed
	 *    <li>ERROR_NULL_ARGUMENT when items is null
	 * </ul>
	 * </p>
	 */
	public void setSelection(HexTableItem[] items) {
		TableItem[] tableItems = new TableItem[items.length];

		for (int i = 0; i < items.length; i++) {
			if (items[i] == null) throw new SWTError(SWT.ERROR_NULL_ARGUMENT);
			tableItems[i] = items[i].getItem();
		} // for

		setSelection(tableItems);
	}
	
	/**
	 * Sets the selection.
	 * <p>
	 * @param items new selection
	 *
	 * @exception SWTError <ul>
	 *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread
	 *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed
	 *    <li>ERROR_NULL_ARGUMENT when items is null
	 * </ul>
	 * </p>
	 */
	public void setSelection(TableItem[] items) {
		table.setSelection(items);
	}

	/**
	 * Sets the table selection
	 * @param index
	 */
	public void setSelection(int index) {
		table.setSelection(index);
	}

	/**
	 * Gets the selected items.
	 * <p>
	 * This operation will fail if the selected
	 * items cannot be queried from the OS.
	 *
	 * @return the selected items in the widget
	 *
	 * @exception SWTError <ul>
	 *          <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
	 *          <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
	 *          <li>ERROR_CANNOT_GET_SELECTION when the operation fails</li>
	 * </ul>
	 * </p
	 */
	public HexTableItem[] getSelection() {
		TableItem[] selection = table.getSelection();

		HexTableItem[] result = new HexTableItem[selection.length];

		for (int i = 0; i < selection.length; i++){
			result[i] = (HexTableItem) selection[i].getData();
		} // for

		return result;
	}

	/**
	 * Gets the number of selected items.
	 * <p>
	 * This operation will fail if the number of selected
	 * items cannot be queried from the OS.
	 *
	 * @return the number of selected items in the widget
	 *
	 * @exception SWTError <ul>
	 *          <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
	 *          <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
	 *          <li>ERROR_CANNOT_GET_COUNT when the operation fails</li>
	 * </ul>
	 * </p>
	 */
	public int getSelectionCount() {
		return table.getSelectionCount();
	}
	
	/**
	 * Gets the selection index
	 * @return selection index
	 */
	public int getSelectionIndex() {
		return table.getSelectionIndex();
	}

	/**
	 * Returns the table
	 * @return the table
	 */
	public Table getTable() {
		return table;
	}
	
	/**
	 * Returns the flag indicating whether the "dispose" process is running or not
	 * @return the flag
	 */
	public boolean isInDispose() {
		return inDispose;
	}

	/**
	 * Returns widget size
	 * @param wHint
	 * @param hHint
	 */
	public Point computeSize(int wHint, int hHint) {
		return computeSize(hHint, wHint, true);
	}

	/**
	 * Returns widget size
	 * @param wHint
	 * @param hHint
	 * @param changed
	 */
	public Point computeSize(int wHint, int hHint, boolean changed) {
		super.computeSize(wHint, hHint, changed);
		return table.computeSize(wHint, hHint, changed);
	}

	/**
	 * Computes the widget trim.
	 * <p>
	 * Trim is widget specific and may include scroll
	 * bars and menu bar in addition to other trimmings
	 * that are outside of the widget's client area.
	 *
	 * @param x the x location of the client area
	 * @param y the y location of the client area
	 * @param width the width of the client area
	 * @param height the height of the client area
	 * @return a rectangle containing the trim of the widget.
	 *
	 * @exception SWTError <ul>
	 *          <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
	 *          <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
	 * </ul>
	 * </p>
	 */
	public Rectangle computeTrim(int x, int y, int width, int height) {
		return table.computeTrim(x, y, width, height);
	}

	/**
	 * Gets the height of one item.
	 * <p>
	 * This operation will fail if the height of
	 * one item could not be queried from the OS.
	 *
	 * @return the height of one item in the widget
	 *
	 * @exception SWTError <ul>
	 *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread
	 *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed
	 *    <li>ERROR_CANNOT_GET_ITEM_HEIGHT when the operation fails
	 * </ul>
	 * </p>
	 */
	public int getItemHeight() {
		return table.getItemHeight();
	}

	/**
	 * Calculates the number of cells occupied by data
	 * @return the number of cells occupied by data (table size)
	 */
	public int getTableSize() {
		int lastItemIndex = getItemCount()-1;
		if (getItem(lastItemIndex) == null) {
			//
			// ERROR???
			//
			MessageDialog.openInformation(getShell(), "EHEP", "LastItem is NULL!! lastItemIndex=" + lastItemIndex + ", items.size=" + items.size() + "\n\nPlease report this error to the author. Thanks.");
			EhepPlugin.log("LastItem=null!!! lastItemIndex=" + lastItemIndex + ", items.len=" + items.size());
		}
		return lastItemIndex * EHEP.TABLE_NUM_DATA_COLUMNS + getItem(lastItemIndex).getItemSize();
	}

	/**
	 * Calculates the number of cells occupied by the data from the specified position to the end
	 * @param rowIndex
	 * @param columnIndex
	 * @return the number of cells occupied by data
	 */
	public int getTableSize(int rowIndex, int columnIndex) {
		int totalSize = getTableSize();
		return totalSize - (rowIndex * EHEP.TABLE_NUM_DATA_COLUMNS + columnIndex);
	}

	/**
	 * Calculates the number of cells occupied by the data in specified region
	 * @param rowIndexBegin
	 * @param columnIndexBegin
	 * @param rowIndexEnd
	 * @param columnIndexEnd
	 * @return the number of cells occupied by data
	 */
	public int getTableSize(int rowIndexBegin, int columnIndexBegin, int rowIndexEnd, int columnIndexEnd) {
		return (rowIndexEnd * EHEP.TABLE_NUM_DATA_COLUMNS + columnIndexEnd + 1) - (rowIndexBegin * EHEP.TABLE_NUM_DATA_COLUMNS + columnIndexBegin);
	}

	/**
	 * Sets the widget background color.
	 * <p>
	 * When new color is null, the background reverts
	 * to the default system color for the widget.
	 *
	 * @param color the new color (or null)
	 * 
	 * @exception SWTError <ul>
	 *          <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
	 *          <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
	 * </ul>
	 * </p>
	 */
	public void setBackground(Color color) {
		super.setBackground(color);
		table.setBackground(color);
	}

	/**
	 * Sets the widget foreground color.
	 * <p>
	 * When new color is null, the foreground reverts
	 * to the default system color for the widget.
	 *
	 * @param color the new color (or null)
	 * 
	 * @exception SWTError <ul>
	 *          <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
	 *          <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
	 * </ul>
	 * </p>
	 */
	public void setForeground(Color color) {
		super.setForeground(color);
		table.setForeground(color);
	}

	/**
	 * Sets the widget background color.
	 * <p>
	 * When new color is null, the background reverts
	 * to the default system color for the widget.
	 *
	 * @param color the new color (or null)
	 * 
	 * @exception SWTError <ul>
	 *          <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
	 *          <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
	 * </ul>
	 * </p>
	 */
	public void setCellsBackground(int row, int col, int size, Color color) {
		col++;
		
		HexTableItem item =getItem(row);
		while(size!=0){
			item.setBackground(col, color);
			col++;
			if(col>EHEP.TABLE_NUM_DATA_COLUMNS){
				row++;
				item = getItem(row);
				col = 1;
			}
			size--;
		}
	}
	
	/**
	 * Sets the widget foreground color.
	 * <p>
	 * When new color is null, the foreground reverts
	 * to the default system color for the widget.
	 *
	 * @param color the new color (or null)
	 * 
	 * @exception SWTError <ul>
	 *          <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
	 *          <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
	 * </ul>
	 * </p>
	 */
	public void setCellsForeground(int row, int col, int size, Color color) {
		col++;
		
		HexTableItem item =getItem(row);
		while(size!=0){
			item.setForeground(col, color);
			col++;
			if(col>EHEP.TABLE_NUM_DATA_COLUMNS){
				row++;
				item = getItem(row);
				col = 1;
			}
			size--;
		}
	}	
	
	/**
	 * Sets the widget background color.
	 * <p>
	 * When new color is null, the background reverts
	 * to the default system color for the widget.
	 *
	 * @param color the new color (or null)
	 * 
	 * @exception SWTError <ul>
	 *          <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
	 *          <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
	 * </ul>
	 * </p>
	 */
	public void setCellBackground(int row, int col, Color color) {
		getItem(row).setBackground(col, color);
	}	
	
	/**
	 * Sets the widget foreground color.
	 * <p>
	 * When new color is null, the foreground reverts
	 * to the default system color for the widget.
	 *
	 * @param color the new color (or null)
	 * 
	 * @exception SWTError <ul>
	 *          <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
	 *          <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
	 * </ul>
	 * </p>
	 */
	public void setCellForeground(int row, int col, Color color) {
		getItem(row).setForeground(col, color);
	}	
	
	/**
	 * Sets the pop up menu.
	 * <p>
	 * Every control has an optional pop up menu that is
	 * displayed when the user requests a popup menu for
	 * the control.  The sequence of key strokes/button
	 * presses/button releases that is used to request
	 * a pop up menu is platform specific.
	 *
	 * @param menu the new pop up menu
	 *
	 * @exception SWTError <ul>
	 *          <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
	 *          <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
	 *          <li>ERROR_MENU_NOT_POP_UP when the menu is not a POP_UP</li>
	 *          <li>ERROR_NO_COMMON_PARENT when the menu is not in the same widget tree</li>
	 * </ul>
	 * </p>
	 */
	public void setMenu(Menu menu) {
		super.setMenu(menu);
		table.setMenu(menu);
	}

	/**
	 * Sets the tool tip text.
	 * <p>
	 * @param string the new tool tip text (or null)
	 *
	 * @exception SWTError <ul>
	 *          <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
	 *          <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
	 * </ul>
	 * </p>
	 */
	public void setToolTipText(String string) {
		super.setToolTipText(string);
		table.setToolTipText(string);
	}

	/**
	 * Sets layout data
	 * @param layoutData
	 */
	public void setLayoutData(Object layoutData) {
		super.setLayoutData(layoutData);
		table.setLayoutData(layoutData);
	}

	/**
	 * Sets data
	 * @param data
	 */
	public void setData(Object data) {
		super.setData(data);
		table.setData(data);
	}

	/**
	 * Sets the focus
	 */
	public boolean setFocus() {
		super.setFocus();
		return table.setFocus();
	}

	/**
	 * Shows the selection.
	 * <p>
	 * If there is no selection or the selection
	 * is already visible, this method does nothing.
	 * If the selection is scrolled out of view,
	 * the top index of the widget is changed such
	 * that selection becomes visible.
	 *
	 * @exception SWTError <ul>
	 *    <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread
	 *    <li>ERROR_WIDGET_DISPOSED when the widget has been disposed
	 * </ul>
	 * </p
	 */
	public void showSelection() {
		table.showSelection();
	}

	/**
	 * @see #computeSize
	 */
	public void pack() {
		checkWidget();
		setSize(computeSize(SWT.DEFAULT, SWT.DEFAULT));
	}
	
	/**
	 * Recalculates the size
	 */
	public void onResize() {
		Rectangle area = getClientArea();
		table.setBounds(0, 0, area.width, area.height);
	}

	/**
	 * Sets the focus (event handler)
	 */
	public void onFocusIn() {
		table.setFocus();
	}

	/**
	 * Disposes the table
	 */
	public void onDispose() {
		inDispose = true;
        items.clear();
		inDispose = false;
	}

	/**
	 * Adds data to the hex table (at the end)
	 * @param bufferIndex zero-based buffer index
	 * @param buffer with data
	 * @param dataAvailable amount of data available in the buffer
	 */
	public void addData(int bufferIndex, byte[] buffer, int dataAvailable) {
		if (dataAvailable < 0 || buffer == null || buffer.length == 0) {
			//
			// No data available or no buffer specified - do nothing
			//
			return;
		}
		
		HexTableItem item;
		int dataStartIndex = 0;
		int numCompleteRows, lastRowLength, numRows;
		
		//
		// Get last table row (if exists)
		//
		numRows = getItemCount();
		item = getItem(numRows-1);
				
		if (item != null) {
			//
			// Let's add data into the last table row (if possible)
			//
			dataStartIndex = item.addData(bufferIndex, numRows, buffer, dataAvailable, 0);
		} // if

		//
		// Calculate the number of complete table rows that can be filled with the rest of the data
		//
		numCompleteRows = (dataAvailable - dataStartIndex) / EHEP.TABLE_NUM_DATA_COLUMNS;

		//
		// Calculate the length of the last table row in characters
		//
		lastRowLength = (dataAvailable - dataStartIndex) % EHEP.TABLE_NUM_DATA_COLUMNS;

		//
		// Add full table rows
		//
		for (int i = 0; i < numCompleteRows; i++) {
			item = new HexTableItem(this, SWT.NONE);
			item.addData(bufferIndex, i, buffer, dataAvailable, dataStartIndex + i * EHEP.TABLE_NUM_DATA_COLUMNS);
		} // for
		
		if (lastRowLength > 0) {
			//
			// Add data into the last and incomplete table row
			//
			item = new HexTableItem(this, SWT.NONE);
			item.addData(bufferIndex, numCompleteRows, buffer, dataAvailable, dataStartIndex + numCompleteRows * EHEP.TABLE_NUM_DATA_COLUMNS);
		} // if
	}
	
	/**
	 * Inserts new data into the table from the specified position
	 * @param dataSize
	 * @param dataValue
	 * @param rowIndex
	 * @param columnIndex
	 * @param refresh
	 * @param buffer if buffer is NOT null, then insert buffer into table instead of dataValue
	 */
	public void insertData(int dataSize, int dataValue, int rowIndex, int columnIndex, boolean refresh, byte [] buffer) {
		EhepPlugin.log("insertData(): dataSize=" + dataSize + ", dataValue=" + dataValue + ", rowIndex=" + rowIndex + ", columnIndex=" + columnIndex + ", refresh=" + refresh+", buffer=" + buffer);
		
		if(buffer!=null)
			dataSize = buffer.length;

		//
		// Backup table from the position where user wants to insert new data to the end
		//
		HexTableBuffer tableBuffer = new HexTableBuffer(this);
		tableBuffer.storeTableRegion(new HexTablePointer(rowIndex, columnIndex));
		
		//
		// Set "busy" cursor
		//
		Cursor waitCursor = new Cursor(table.getDisplay(), SWT.CURSOR_WAIT);
		table.getShell().setCursor(waitCursor);

		table.setRedraw(false);

		//
		// Get the last item size
		//
		int lastItemIndex = getItemCount()-1;

		HexTableItem lastItem = getItem(lastItemIndex);
		int lastItemSize = lastItem.getItemSize(); // 1...16
		int lastItemFreeSize = EHEP.TABLE_NUM_DATA_COLUMNS - lastItemSize; //0...15
		int numNewItemsToInsert = 0;
		int numNewCellsToInsert = 0;

		if (dataSize - lastItemFreeSize > 0) {
			//
			// Table must be expanded in order to insert required amount of new data
			//
			// Get number of full items
			//
			numNewItemsToInsert = (dataSize - lastItemFreeSize) / EHEP.TABLE_NUM_DATA_COLUMNS;

			//
			// Get number of cells of the last (incomplete) item to add
			//
			numNewCellsToInsert = (dataSize - lastItemFreeSize) % EHEP.TABLE_NUM_DATA_COLUMNS;

			//
			// Add new items
			//
			int style = getStyle();
			
			for (int i = 0; i < numNewItemsToInsert; i++) {
				HexTableItem newItem = new HexTableItem(this, style, lastItemIndex+1);
				newItem.getParent(); // just to supress warning
			} // for
			
			//
			// Add one more item if neccessary (at the end of the table)
			//
			if (numNewCellsToInsert > 0) {
				HexTableItem newItem = new HexTableItem(this, style, getItemCount());
				newItem.getParent(); // just to supress warning
			} // if
		} // if

		//
		// Put new data to the table
		//
		String dataValueString = null;
		if(buffer==null)
			dataValueString = Utils.byte2string((byte) dataValue);
		
		int row = rowIndex;
		int column = columnIndex+1;
		HexTableItem item = getItem(row);

		EhepPlugin.log("insertData(): inserting data: row=" + row + ", column=" + column);
		
		for (int i = 0; i < dataSize; i++) {
			if (column > EHEP.TABLE_NUM_DATA_COLUMNS) {
				item = getItem(++row);
				column = 1;
			} // if

			EhepPlugin.log("insertData(" + i + "): row=" + row + ", column=" + column);
			
			//
			// Specify new cell content
			//
			if(buffer==null)
				item.setText(column, dataValueString);
			else
				item.setText(column, Utils.byte2string(buffer[i]));

			//
			// Mark changed table cells
			//
			item.setBackground(column, EhepPlugin.getColor(PreferenceConverter.getColor(EhepPlugin.getDefault().getPreferenceStore(), EHEP.PROPERTY_COLOR_BACKGROUND_INSERTED_CELL)));
			item.setForeground(column, EhepPlugin.getColor(PreferenceConverter.getColor(EhepPlugin.getDefault().getPreferenceStore(), EHEP.PROPERTY_COLOR_FOREGROUND_INSERTED_CELL)));
			
			column++;
		} // for
		
		//
		// Put old data back from backup to the table
		//
		tableBuffer.restoreTableRegion(new HexTablePointer(rowIndex, columnIndex).move(dataSize));
		
		if (refresh) {
			//
			// Refresh first and last table column according to new data
			// 
			refreshOffsetColumn();
			refreshCharacterView();
			hexEditorControl.getCursor().redraw();
		} // if
		
		table.setRedraw(true);
		table.redraw();

		//
		// Set the standard cursor
		//
		table.getShell().setCursor(null);
		waitCursor.dispose();

		tableBuffer.dispose();
	}

	/**
	 * Appends new data at the end of the table
	 * @param dataSize
	 * @param dataValue
	 * @param rowIndex
	 * @param columnIndex
	 * @param refresh
	 */
	public void appendData(int dataSize, int dataValue, int rowIndex, int columnIndex, boolean refresh) {
		String dataValueString = Utils.byte2string((byte) dataValue);
		HexTableItem lastItem = getItem(rowIndex);
		int lastItemSize = lastItem.getItemSize(); // 1...16
		int lastItemFreeSize = EHEP.TABLE_NUM_DATA_COLUMNS - lastItemSize; //0...15
		int numNewItemsToAppend = 0;
		int numNewCellsToAppend = 0;
		
		table.setRedraw(false);
		
		//
		// Append data on the last row (if possible)
		//
		for (int i = 0; i < lastItemFreeSize && lastItem != null && dataSize > 0; i++) {
			lastItem.setText(lastItemSize+i+1, dataValueString);
			dataSize--;

			//
			// Mark changed table rows/items
			//
			lastItem.setBackground(lastItemSize+i+1, EhepPlugin.getColor(PreferenceConverter.getColor(EhepPlugin.getDefault().getPreferenceStore(), EHEP.PROPERTY_COLOR_BACKGROUND_INSERTED_CELL)));
			lastItem.setForeground(lastItemSize+i+1, EhepPlugin.getColor(PreferenceConverter.getColor(EhepPlugin.getDefault().getPreferenceStore(), EHEP.PROPERTY_COLOR_FOREGROUND_INSERTED_CELL)));
		} // for
		
		if (dataSize > 0) {
			//
			// Get number of full items
			//
			numNewItemsToAppend = dataSize / EHEP.TABLE_NUM_DATA_COLUMNS;

			//
			// Get number of cells of the last (incomplete) item to append
			//
			numNewCellsToAppend = dataSize % EHEP.TABLE_NUM_DATA_COLUMNS;
			
			//
			// Append new items
			//
			int style = getStyle();
			
			for (int i = 0; i < numNewItemsToAppend; i++) {
				HexTableItem newItem = new HexTableItem(this, style, rowIndex + 1);

				for (int j = 0; j < EHEP.TABLE_NUM_DATA_COLUMNS && dataSize > 0; j++) {
					//
					// Set text
					//
					newItem.setText(j+1, dataValueString);

					//
					// Mark changed table cell
					//
					newItem.setBackground(j+1, EhepPlugin.getColor(PreferenceConverter.getColor(EhepPlugin.getDefault().getPreferenceStore(), EHEP.PROPERTY_COLOR_BACKGROUND_INSERTED_CELL)));
					newItem.setForeground(j+1, EhepPlugin.getColor(PreferenceConverter.getColor(EhepPlugin.getDefault().getPreferenceStore(), EHEP.PROPERTY_COLOR_FOREGROUND_INSERTED_CELL)));

					dataSize--;
				} // for
			} // for
			
			//
			// Append one more item if neccessary (at the end of the table)
			//
			if (numNewCellsToAppend > 0) {
				HexTableItem newItem = new HexTableItem(this, style, getItemCount());

				for (int j = 0; j < dataSize; j++) {
					newItem.setText(j+1, dataValueString);
					//
					// Mark changed table rows/items
					//
					newItem.setBackground(j+1, EhepPlugin.getColor(PreferenceConverter.getColor(EhepPlugin.getDefault().getPreferenceStore(), EHEP.PROPERTY_COLOR_BACKGROUND_INSERTED_CELL)));
					newItem.setForeground(j+1, EhepPlugin.getColor(PreferenceConverter.getColor(EhepPlugin.getDefault().getPreferenceStore(), EHEP.PROPERTY_COLOR_FOREGROUND_INSERTED_CELL)));
				} // for
				
				newItem.itemFiller();
			} // if
		} // if

		if (refresh) {
			//
			// Refresh first and last table column according to new data
			// 
			refreshOffsetColumn();
			refreshCharacterView();
			hexEditorControl.getCursor().redraw();
		} // if

		table.setRedraw(true);
		table.redraw();
	}

	/**
	 * Created by: Sophia Ghins
	 * method to verify that the item data matches with the expected
	 * (currenty verifying that the last cell in the hex table is equal to ASCII value 'C')
	 * @param data
	 * @param rowIndex
	 * @param columnIndex
	 * @return whether data matches or not
	 * Date: 05/06/09
	 */
	public boolean verifyData(String data, int rowIndex){
		String itemtxt;
		//String itemStr;
		HexTableItem lastItem = getItem(rowIndex); //item in last row 
		int lastItemSize = lastItem.getItemSize(); // 1...16
		if(lastItemSize > 0){
			itemtxt = lastItem.getText(lastItemSize);
			itemtxt =Utils.hexToChars(itemtxt);
			if(data.equals(itemtxt)){
				return true;
			}
		}
		return false;
		
	}
	
	/**
	 * Deletes specified amount of data from the table from specified position
	 * @param delSize
	 * @param rowIndex
	 * @param columnIndex
	 * @param refresh
	 */
	public void deleteData(int delSize, int rowIndex, int columnIndex, boolean refresh) {
		EhepPlugin.log("deleteData(): delSize=" + delSize + ", rowIndex=" + rowIndex + ", columnIndex=" + columnIndex + ", refresh=" + refresh);
		
		//
		// rowIndex, columnIndex are zero-based indexes to data-part of the table
		//
		if (delSize == 0) return;
		
		int tableSizeFromCursor = getTableSize(rowIndex, columnIndex);
		
		EhepPlugin.log("deleteData(): tableSizeFromCursor=" + tableSizeFromCursor);
		
		table.setRedraw(false);
		
		if (delSize == -1 || tableSizeFromCursor <= delSize) {
			//
			// Delete all data from cursor position to the end
			//
			HexTableItem item = getItem(rowIndex);
			item.delete(columnIndex);

			if (columnIndex == 0) {
				int numItems = getItemCount();
				
				if (rowIndex > 0) {
					remove(rowIndex);
					hexEditorControl.getCursor().setSelection(rowIndex-1, 1);
				}
				else {
					//
					// rowIndex == 0
					//
					if (numItems > 1) remove(1);
					hexEditorControl.getCursor().setSelection(0, 1);
				}
			} // if
			else {
				remove(rowIndex + 1);
			}
		} // if
		else {
			//
			// Prepare two table pointers
			//
			HexTablePointer p1 = new HexTablePointer(rowIndex, columnIndex);
			HexTablePointer p2 = new HexTablePointer(rowIndex, columnIndex).move(delSize);
			
			EhepPlugin.log("deleteData(): p1=" + p1.toString());
			EhepPlugin.log("deleteData(): p2=" + p2.toString());
			
			//
			// Backup data
			//
			HexTableBuffer tableBuffer = new HexTableBuffer(this);
			tableBuffer.storeTableRegion(p2);
			
			//
			// Restore data 
			//
			int storedRegionSize = tableBuffer.getTableRegionSize();
			tableBuffer.restoreTableRegion(p1);
			p1.move(storedRegionSize);
			
			//
			// Remove unused items/cells
			//
			if (p1.getColumnIndex() == 0) {
				remove(p1.getRowIndex());
			} // if
			else {
				HexTableItem item = getItem(p1.getRowIndex());
				item.delete(p1.getColumnIndex());
				remove(p1.getRowIndex()+1);
			} // else
			
			tableBuffer.dispose();
		} // else

		if (refresh) {
			//
			// Refresh first and last table column according to new data
			// 
			refreshOffsetColumn();
			refreshCharacterView();
			hexEditorControl.getCursor().redraw();
		} // if
		
		table.setRedraw(true);
		table.redraw();
	}

	/**
	 * Copies data from the table from certain row into the internal buffer
	 * @param buffer data buffer
	 * @param rowIndex row number
	 * @return number of bytes copied into the buffer
	 */
	public int getRowData(byte[] buffer, int rowIndex) {
		HexTableItem row = getItem(rowIndex);
		String hexString;

		//
		// Loop for each column in the particular table row
		//
		for (int i = 0; i < EHEP.TABLE_NUM_DATA_COLUMNS; i++) {
			hexString = row.getText(i+1);

			//
			// Set table row color
			//
			row.setBackground(i+1, EhepPlugin.getColor(PreferenceConverter.getColor(EhepPlugin.getDefault().getPreferenceStore(), EHEP.PROPERTY_COLOR_BACKGROUND_TABLE)));
			row.setForeground(i+1, EhepPlugin.getColor(PreferenceConverter.getColor(EhepPlugin.getDefault().getPreferenceStore(), EHEP.PROPERTY_COLOR_FOREGROUND_TABLE)));

			if (hexString.equals(EHEP.TABLE_EMPTY_CELL)) {
				//
				// First empty table cell reached - the "end-of-data" mark
				//
				for (int j = (i+1); j < EHEP.TABLE_NUM_DATA_COLUMNS; j++) {
					//
					// Set table row color
					//
					row.setBackground(j, EhepPlugin.getColor(PreferenceConverter.getColor(EhepPlugin.getDefault().getPreferenceStore(), EHEP.PROPERTY_COLOR_BACKGROUND_TABLE)));
					row.setForeground(j, EhepPlugin.getColor(PreferenceConverter.getColor(EhepPlugin.getDefault().getPreferenceStore(), EHEP.PROPERTY_COLOR_FOREGROUND_TABLE)));
				}
				return i;
			} // if

			//
			// Convert hex string to byte and put byte into the buffer
			//
			buffer[i] = Utils.string2byte(hexString);
		} // for
		
		//
		// At this point entire row was processed
		//
		return EHEP.TABLE_NUM_DATA_COLUMNS;
	}

	/**
	 * Refreshes/recalculates offset column for entire table
	 */
	public void refreshOffsetColumn() {
		HexTableItem item;
		String offset;
		String offsetZeroPadded;
		
		for (int i = 0; i < getItemCount(); i++) {
			item = getItem(i);
			if (item == null) continue;
			//
			// Calculate the offset
			//
			offset = Integer.toHexString(i * EHEP.TABLE_NUM_DATA_COLUMNS).toUpperCase();
			offsetZeroPadded = "00000000".substring(offset.length()) + offset + ":";
			item.setText(0, offsetZeroPadded);
		} // for
	}
	
	public void refreshCharacterView(int row){
		getItem(row).refreshCharacterView();
	}

	/**
	 * Refreshes character representaion of hex data with current encoding
	 * @return true if success otherwise false
	 */
	public boolean refreshCharacterView() {
		return refreshCharacterView(hexEditorControl.getCurrentEncoding());
	}

	/**
	 * Displays character representaion of hex data with required encoding for entire table
	 * @param encodingType required encoding (canonical encoding name as a string)
	 * @return true if success otherwise false
	 */
	public boolean refreshCharacterView(final String encoding) {
		final ProgressMonitorDialog monitorDialog = new ProgressMonitorDialog(getShell());
		monitorDialog.setOpenOnRun(false);
		//
		// Define a long running operation
		//
		IRunnableWithProgress op = new IRunnableWithProgress() {
			public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
				HexTableItem row;
				int numItems = getItemCount();
				long openTime = System.currentTimeMillis() + 2000L;
				boolean opened = false;
				
				monitor.beginTask("Converting encoding to " + encoding + "...", numItems);
				
				for (int i = 0; i < numItems; i++) {
					if (!opened && System.currentTimeMillis() > openTime) {
						monitorDialog.open();
						opened = true;
					}
					//
					// Loop for each row
					//
					if (i % 10 == 0) monitor.subTask("Row #" + i + " (" + (100*i/numItems) + "%)");
					row = getItem(i);
					
					if (row != null && row.refreshCharacterView(encoding) == false) {
						//
						// In case of some error or exception - stop looping
						//
						EhepPlugin.log("refreshCharacterView(String): EXCEPTION!");
						throw new InterruptedException();
					} // if
					
					monitor.worked(1);
				} // for
				
				monitor.done();
			} // run()
		};

		try {
			monitorDialog.run(false, false, op);
		}
		
		catch (InvocationTargetException e) {
			//
			// Handle exception
			//
			Throwable realCause = e.getCause();
			Throwable realException = e.getTargetException();
			MessageDialog.openError(getShell(), EHEP.DIALOG_TITLE_EXCEPTION, "Exception: " + e.toString() + "\n\n" + realException.getMessage() + "\n\nCause: " + realCause.getMessage());
			return false;
		}
		
		catch (InterruptedException e) {
			//
			// Handle cancelation
			//
			return false;
		}

		return true;
	}

	/**
	 * Inserts an empty "dummy" item
	 */
	public void addDummyItem() {
		HexTableItem item = new HexTableItem(this, SWT.NONE);

		for (int i = 0; i < EHEP.TABLE_NUM_DATA_COLUMNS; i++) {
			//
			// Read buffer, convert bytes to strings and store them into the row
			//
			item.setText(i+1, EHEP.TABLE_EMPTY_CELL);
		} // for

		//
		// Compress the size required for offset displaying
		//
		packColumns();
	}

	/**
	 * Compresses the size of all table columns
	 */
	public void packColumns() {
		for (int i = 0; i < EHEP.TABLE_NUM_COLUMNS; i++) {
			TableColumn tableColumn = table.getColumn(i);
			tableColumn.pack();
			tableColumn.setWidth(tableColumn.getWidth() * 6 / 5);
		} // for
	}
	
//	/**
//	 * value depends on whether buffer has been overwritten
//	 * @author Sophia Ghins
//	 * Date: 06/20/09
//	 */	
//	public void setbufOverwrite(boolean overWriteVal){
//		this.bufOverwrite = overWriteVal;
//	}

	/**
	 * Overwrite buffer data to the hex table
	 * @param bufferIndex zero-based buffer index
	 * @param buffer with data
	 * @param dataAvailable amount of data available in the buffer
	 * @author Sophia Ghins on Date: 06/20/09
	 */
	public int overwriteBufData(int bufferIndex, byte[] buffer, int dataAvailable) {
		int dataCounter = 0;
		int dataStartIndex = 0;
		int numCompleteRows, lastRowLength, numRows;
		HexTableItem item;
		if (dataAvailable < 0 || buffer == null || buffer.length == 0) {
			return -1;
		}
		
		if(items.size() > 0){
			item = getItem(items.size() - 1);
		}else{
			return -1;     //display as error
		}
		
		// Calculate the number of complete table rows that can be filled with the rest of the data
		numCompleteRows = (dataAvailable - dataStartIndex) / EHEP.TABLE_NUM_DATA_COLUMNS;

		// Calculate the length of the last table row in characters
		lastRowLength = (dataAvailable - dataStartIndex) % EHEP.TABLE_NUM_DATA_COLUMNS;

		// Add full table rows
		for (int i = 0; i < numCompleteRows; i++) {
			item = getItem(i);
			item.overwriteData(bufferIndex, i, buffer, dataAvailable, dataStartIndex + i * EHEP.TABLE_NUM_DATA_COLUMNS);
		} // for
		
		// Add data into the last and incomplete table row
		if (lastRowLength > 0) {
			item = getItem(numCompleteRows);
			dataCounter = item.overwriteData(bufferIndex, numCompleteRows, buffer, dataAvailable, dataStartIndex + numCompleteRows * EHEP.TABLE_NUM_DATA_COLUMNS);
		} // if
		return dataCounter;
	}
	
	/**
	 * Overwrite modified buffer data to the hex table at initRowIndex
	 * @param bufferIndex zero-based buffer index
	 * @return no of rows overwritten
	 * @author Sophia Ghins on Date: 06/26/09
	 */
	public int overwriteBufDataInTable(byte[] buffer) {
		int dataStartIndex = 0;
		int bufferIndex = 0;
		int bStartIndex = 0;
		HexTableItem item;
		
		if (buffer == null || buffer.length == 0) {
			return -1;
		}
				
		
		if(items.size() > 0){
			item = getItem(getInitRowIndex());
		}else{
			return -1;     //display as error
		}
		
		// Calculate the length of the last table row in characters
		//lastRowLength = (dataAvailable - dataStartIndex) % EHEP.TABLE_NUM_DATA_COLUMNS;

		// Add full table rows
		for (int i = getInitRowIndex(); i < getItemCount(); i++) {
			item = getItem(i);
			item.overwriteData(bufferIndex, i, buffer, getBufSize(), dataStartIndex + bStartIndex * EHEP.TABLE_NUM_DATA_COLUMNS);
			bStartIndex++;
		} // for
		
		return bStartIndex;
				
	}
	
	/**
	 * Copies last 80 bytes (5 rows) or entire table into the buffer.
	 * @return number of bytes copied into the buffer
	 * @author Sophia Ghins
	 * Date: 06/26/09
	 */
	public int insertTableDataInBuf(){
		int bufDataCounter = 0;
		int bufIndex = 0;
		int totalRows = getItemCount();
		setBufSize(0);
		if(getTableSize() > 80){
			int rowIndex = totalRows - 5;
			setInitRowIndex(rowIndex);
			for(;rowIndex<totalRows;rowIndex++){
				synchronized (FileManager.buffer) {
					bufIndex += insertRowDataInBuf(FileManager.buffer, rowIndex, bufIndex);
				}
			}
		}else{
			setInitRowIndex(0);
			for(int rowIndex=0;rowIndex<totalRows;rowIndex++){
				synchronized (FileManager.buffer) {
					bufIndex += insertRowDataInBuf(FileManager.buffer, rowIndex, bufIndex);
				}
			}
		}
		return bufSize;
	}

	/**
	 * Copies data from the table from certain row into the corresponding 
	 * location in the buffer.
	 * @param buffer data buffer
	 * @param rowIndex row number
	 * @return number of bytes copied into the buffer
	 * @author Sophia Ghins
	 * Date: 06/20/09
	 */
	public int insertRowDataInBuf(byte[] buffer, int rowIndex, int bufIndex) {
		HexTableItem row = getItem(rowIndex);
		String hexString;

		//
		// Loop for each column in the particular table row
		//
		for (int i = 0; i < EHEP.TABLE_NUM_DATA_COLUMNS; i++) {
			hexString = row.getText(i+1);

			//
			// Set table row color
			//
			row.setBackground(i+1, EhepPlugin.getColor(PreferenceConverter.getColor(EhepPlugin.getDefault().getPreferenceStore(), EHEP.PROPERTY_COLOR_BACKGROUND_TABLE)));
			row.setForeground(i+1, EhepPlugin.getColor(PreferenceConverter.getColor(EhepPlugin.getDefault().getPreferenceStore(), EHEP.PROPERTY_COLOR_FOREGROUND_TABLE)));

			if (hexString.equals(EHEP.TABLE_EMPTY_CELL)) {
				//
				// First empty table cell reached - the "end-of-data" mark
				//
				for (int j = (i+1); j < EHEP.TABLE_NUM_DATA_COLUMNS; j++) {
					//
					// Set table row color
					//
					row.setBackground(j, EhepPlugin.getColor(PreferenceConverter.getColor(EhepPlugin.getDefault().getPreferenceStore(), EHEP.PROPERTY_COLOR_BACKGROUND_TABLE)));
					row.setForeground(j, EhepPlugin.getColor(PreferenceConverter.getColor(EhepPlugin.getDefault().getPreferenceStore(), EHEP.PROPERTY_COLOR_FOREGROUND_TABLE)));
				}
				return i;
			} // if

			//
			// Convert hex string to byte and put byte into the buffer
			//
			byte btst = Utils.string2byte(hexString);
			buffer[bufIndex + i] = Utils.string2byte(hexString);
			bufSize++;
		} // for
		
		//
		// At this point entire row was processed
		//
		return EHEP.TABLE_NUM_DATA_COLUMNS;
	}

	/**
	 * sets the row index at which the contents were copied to buffer
	 * @param row index at which the contents were copied to buffer
	 * @author Sophia Ghins
	 * Date: 06/26/09
	 */
	public void setInitRowIndex(int initIndex){
		initRowIndex = initIndex;
	}
	
	/**
	 * gets the row index at which the contents were copied to buffer
	 * @return row index at which the contents were copied to buffer
	 * @author Sophia Ghins
	 * Date: 06/26/09
	 */
	public int getInitRowIndex(){
		return initRowIndex;
	}
	
	/**
	 * size of buffer that was overwritten with rows of table 
	 * for watermarking purpose
	 * @return size of overwritten buffer
	 * @author Sophia Ghins
	 * Date: 06/26/09
	 */
	public int getBufSize(){
		return bufSize;
	}	
	
	/**
	 * size of buffer that is to be overwritten with rows of table 
	 * for watermarking purpose
	 * @param set size of buffer
	 * @author Sophia Ghins
	 * Date: 06/27/09
	 */
	public void setBufSize(int size){
		 this.bufSize = size;
	}	
	
	
	
	
}
